﻿/*
   Created 2009 December 6, Randall Maas
   Copyright (c) 2009, Randall Maas
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:

   Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

   Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

   Neither the name of Blackwood Designs, LLC nor the names of its contributors
   may be used to endorse or promote products derived from this software without
   specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
   THE POSSIBILITY OF SUCH DAMAGE.
*/

using System ;
using System . Drawing;
using System . Drawing . Imaging;
using System . Diagnostics;


/// <summary>
/// This class is used to hold the set of points from the light pen, and to be
/// put on the display.
/// </summary>
partial class LightPaint : ISampleGrabberCB
{
   // Human eye Gray scale, scaled
   const int RedScale   = (int)(0.299 * 2048);
   const int GreenScale = (int)(0.587 * 2048);
   const int BlueScale  = (int)(0.114 * 2048);

   /// <summary>
   /// An array of booleans that indicate whether the pixel is a pen point or not
   /// </summary>
   internal byte[] IsPoint;
   internal byte[] CurrentPoints;

   /// <summary>
   /// The threshold of an any channel to detect whether the point is pen
   /// </summary>
   internal int Threshold = 240*2048;

   /// <summary>
   /// The size of the 
   /// </summary>
   Size _Size;

   /// <summary>
   /// Sets the size of the frame buffer used to track points
   /// </summary>
   public Size Size
   {
      get {return _Size;}
      set
      {
         _Size = value;
         // Allocate the array
         if (null == CurrentPoints || CurrentPoints . Length != 3*Size . Width * Size . Height)
           {
              CurrentPoints = new byte[3*Size . Width * Size . Height];
              IsPoint = new byte[Size . Width * Size . Height];
           }
      }
   }


   #region The methods needed to support the Sample Grabber Interface
   public int SampleCB(double SampleTime, IntPtr pSample)
   {
      return 0;
   }

   /// <summary>
   /// An internal delegate used to receive each of the image things
   /// </summary>
   /// <param name="SampleTime">The time (since start of graph execution) that
   /// this frame was captured</param>
   /// <param name="Buffer">Pointer to the image buffer</param>
   /// <param name="BufferLen">Size of the buffer, in bytes</param>
   /// <returns>0 (recommended)</returns>
   public int BufferCB(double SampleTime, IntPtr Buffer, int BufferLen)
   {
      unsafe
      {
         // This scary construct speeds up the processing of the buffer a lot
         // by 10ms or more.  This is critical in speeding up acsess
         fixed (byte* _CurrentPoints = CurrentPoints)
         fixed (byte* _IsPenPoint   = IsPoint)
         fixed (byte* Bknd         = Bkgnd)
         {
         byte* Buf = (byte*) Buffer;
         int Width3 = _Size.Width*3, Width=_Size.Width;

         BufferLen-= BufferLen%3;
         byte* End = Buf + BufferLen;
         byte* CurrentPoint = _CurrentPoints;
         byte* IsPenPoint = _IsPenPoint;

         // Scan the image for the points brighter than threshold
         if (null == Bkgnd)
           #region Find pen, no background
           // This loop alone can take 15ms on my machine
           for (; Buf != End; CurrentPoint+=3, Buf+=3, IsPenPoint++)
           {
              byte B1= Buf[0];
              byte G1= Buf[1];
              byte R1= Buf[2];

              #region Pen Light Threshold Detector
              // This is the key spot that detects the pen light.  This must be very fast
              // Tweak this in different ways to see what works
              int Bright1 =R1 * RedScale + G1 * GreenScale + B1*BlueScale;
              if (Bright1 < Threshold)
                {
                   // If there is a pen point, put the point there
                   if (0 != *IsPenPoint)
                     {
                        Buf[0] = CurrentPoint[0];
                        Buf[1] = CurrentPoint[1];
                        Buf[2] = CurrentPoint[2];
                     }
                   continue;
                }
              #endregion

              byte B2 = CurrentPoint[0];
              byte G2 = CurrentPoint[1];
              byte R2 = CurrentPoint[2];
              if (B1>B2 || G1>G2 || R1>R2)
                {
                   *IsPenPoint = 1;
                   CurrentPoint[0] = B1;
                   CurrentPoint[1] = G1;
                   CurrentPoint[2] = R1;
                }
           }
           #endregion
         else
           for (int PI=0,I=0; Buf != End;  PI++, I += 3)
           {
              byte B1= Buf[0];
              byte G1= Buf[1];
              byte R1= Buf[2];
              byte B2 = _CurrentPoints[I+0];
              byte G2 = _CurrentPoints[I+1];
              byte R2 = _CurrentPoints[I+2];

              #region Pen Light Threshold Detector
              // This is the key spot that detects the pen light.  This must be very fast
              // Tweak this in different ways to see what works
              if (R1 * RedScale + G1 * GreenScale + B1*BlueScale >= Threshold)
              #endregion
                {
                   if (B1>B2 || G1>G2 || R1>R2)
                     {
                        _IsPenPoint[PI] = 1;
                        _CurrentPoints[I+0] = B1;
                        _CurrentPoints[I+1] = G1;
                        _CurrentPoints[I+2] = R1;
                     }
                   Buf+=3;
                   continue;
                }

              if (0 == _IsPenPoint[PI])
                {
                   // Add the current points
                   B2 = Bknd[I+0];
                   G2 = Bknd[I+1];
                   R2 = Bknd[I+2];
                }

              *Buf++ = B2;
              *Buf++ = G2;
              *Buf++ = R2;
           }
        }
      }

      return 0;
   }
   #endregion


   /// <summary>
   /// This is used to horizontally flip the points buffer
   /// </summary>
   internal void FlipBuffer()
   {
      FlipBuffer(CurrentPoints);
      FlipBuffer1(IsPoint);
   }


   /// <summary>
   /// This is used horizontally flip the pixels in the light-pen images
   /// </summary>
   /// <param name="Buf"></param>
   void FlipBuffer(byte[] Buf)
   {
      int L = _Size . Width*3;
      int End = L*_Size.Height;
      for (int Idx = 0; Idx < End; Idx +=L)
       for (int B = Idx, BEnd = B+L-3; B < BEnd;)
       {
          byte Tmp = Buf[BEnd];
          Buf[BEnd++] = Buf[B];
          Buf[B++]    = Tmp;
          Tmp         = Buf[BEnd];
          Buf[BEnd++] = Buf[B];
          Buf[B++]    = Tmp;
          Tmp         = Buf[BEnd];
          Buf[BEnd]   = Buf[B];
          Buf[B++]    = Tmp;
          BEnd -= 5;
       }
   }


   /// <summary>
   /// This is used to horizontally flip the set of points that indicate where
   /// the pen point is.
   /// </summary>
   /// <param name="Buf"></param>
   void FlipBuffer1(byte[] Buf)
   {
      int L = _Size . Width;
      int End = L*_Size.Height;
      for (int Idx = 0; Idx < End; Idx +=L)
       for (int B = Idx, BEnd = B+L-1; B < BEnd;)
       {
          byte Tmp = Buf[BEnd];
          Buf[BEnd--]= Buf[B];
          Buf[B++]   = Tmp;
       }
   }


   /// <summary>
   /// Puts the current points into the previous buffer and
   /// clears the current set of points
   /// </summary>
   internal void Swap()
   {
      for (int I = IsPoint. Length - 1; I >= 0; I--)
       IsPoint[I] = 0;
      for (int I = CurrentPoints . Length - 1; I >= 0; I--)
       CurrentPoints[I] = 0;
   }
}
